
#include <QBrush>
#include <QColor>
#include <QPainter>
#include <QPen>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QSet>
#include <QTime>
#include <QVector3D>

#include "ai/agent.h"
#include "geometry/quadtree.h"
#include "boids/flockwidget.h"

FlockWidget::FlockWidget(QWidget* pkParent)
    : QWidget(pkParent),
      m_bBodiesVisible(true),
      m_bRangesVisible(false),
      m_bQuadtreeVisible(false)
{
    // Enable mouse tracking.
    setMouseTracking(true);

    // Initialize random seed.
    srand(QTime::currentTime().msec());

    // Connections.
    connect(&m_kTimer, SIGNAL(timeout()), this, SLOT(step()));
    connect(&m_kTimer, SIGNAL(timeout()), this, SLOT(update()));
    connect(&m_kTimer, SIGNAL(timeout()), &m_kTimer, SLOT(start()));

    // Start.
    m_kTimer.setSingleShot(true);
    m_kTimer.setInterval(s_DefaultUpdateDelay);
    m_kTimer.start();
}

FlockWidget::~FlockWidget()
{
    foreach(Agent* pkAgent, m_kAgents)
    {
        delete pkAgent;
        pkAgent = NULL;
    }
}

QSize FlockWidget::minimumSizeHint() const
{
    return QSize(480, 320);
}

QSize FlockWidget::sizeHint() const
{
    return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
}

void FlockWidget::setUpdateDelay(int msec)
{
    m_kTimer.setInterval(msec);
    m_kTimer.start();
}

void FlockWidget::reset()
{
    // Initialize random position & velocity.
    foreach(Agent* pkAgent, m_kAgents)
    {
        pkAgent->moveTo(rand() % static_cast< int >(width()) - width() / 2,
                        0,
                        rand() % static_cast< int >(height()) - height() / 2);


        pkAgent->setVelocity(static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity),
                             static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity),
                             static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity));
    }
}

void FlockWidget::setAgentCount(int iCount)
{
    // Adding needed.
    while(iCount > m_kAgents.count())
    {
        Agent* pkAgent = new Agent(m_kFlock);
        Q_CHECK_PTR(pkAgent);

        m_kFlock.add(*pkAgent);
        m_kAgents.insert(pkAgent);

        pkAgent->moveTo(rand() % static_cast< int >(width()) - width() / 2,
                        0,
                        rand() % static_cast< int >(height()) - height() / 2);


        pkAgent->setVelocity(static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity),
                             static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity),
                             static_cast< qreal >(rand() % (int)Agent::s_MaxVelocity -
                                                  rand() % (int)Agent::s_MaxVelocity));
    }

    // Removal needed.
    while(iCount < m_kAgents.count())
    {
        // Ensure that there is at least one agent.
        if(m_kAgents.isEmpty())
            return;

        // Delete the first agent.
        QSet< Agent* >::Iterator kIt = m_kAgents.begin();
        m_kAgents.erase(kIt);
        m_kFlock.remove(**kIt);
    }

    update();
}

void FlockWidget::setBodiesVisible(bool bVisible)
{
    m_bBodiesVisible = bVisible;
}

void FlockWidget::setRangesVisible(bool bVisible)
{
    m_bRangesVisible = bVisible;
}

void FlockWidget::setQuadtreeVisible(bool bVisible)
{
    m_bQuadtreeVisible = bVisible;
}

void FlockWidget::setRepulsionFactor(int iFactor)
{
    m_kFlock.setRepulsionFactor(static_cast< qreal >(iFactor) / 10);
}

void FlockWidget::setAlignementFactor(int iFactor)
{
    m_kFlock.setAlignementFactor(static_cast< qreal >(iFactor) / 10);
}

void FlockWidget::setAttractionFactor(int iFactor)
{
    m_kFlock.setAttractionFactor(static_cast< qreal >(iFactor) / 10);
}

void FlockWidget::setRepulsionFunction(int iFunctionIndex)
{
    m_kFlock.setRepulsionFunction(static_cast< Flock::BlendingFunction >(iFunctionIndex));
}

void FlockWidget::setAlignementFunction(int iFunctionIndex)
{
    m_kFlock.setAlignementFunction(static_cast< Flock::BlendingFunction >(iFunctionIndex));
}

void FlockWidget::setAttractionFunction(int iFunctionIndex)
{
    m_kFlock.setAttractionFunction(static_cast< Flock::BlendingFunction >(iFunctionIndex));
}

void FlockWidget::setDisturbance(int iDisturbanceIndex)
{
    m_kFlock.setDisturbance(static_cast< Flock::Disturbance >(iDisturbanceIndex));
}

void FlockWidget::setDisturbanceFunction(int iFunctionIndex)
{
    m_kFlock.setDisturbanceFunction(static_cast< Flock::BlendingFunction >(iFunctionIndex));
}

void FlockWidget::setDisturbanceFactor(int iFactor)
{
    m_kFlock.setDisturbanceFactor(static_cast< qreal >(iFactor) / 10);
}

void FlockWidget::setDisturbance(const QVector3D& rkPosition)
{
    m_kFlock.setDisturbancePosition(rkPosition);
}

void FlockWidget::setDisturbance(qreal x, qreal y, qreal z)
{
    m_kFlock.setDisturbancePosition(QVector3D(x, y, z));
}

void FlockWidget::step()
{
    // Solve flocking.
    m_kFlock.update(m_kTimer.interval());

    // Integrate.
    QSet< Agent* > kAgents = m_kFlock.agents();
    foreach(Agent* pkAgent, kAgents)
    {
        // Only integrate active agents.
        if(!pkAgent->isActive())
            continue;

        // Integrate movement and keep inside bounds.
        QVector3D kTarget = pkAgent->position() +
                            pkAgent->velocity() *
                            static_cast< qreal >(m_kTimer.interval()) / 1000;

        if(kTarget.x() < 0)
            kTarget.setX(kTarget.x() + width());
        else if(kTarget.x() > width())
            kTarget.setX(kTarget.x() - width());

        kTarget.setY(0);

        if(kTarget.z() < 0)
            kTarget.setZ(kTarget.z() + height());
        else if(kTarget.z() > height())
            kTarget.setZ(kTarget.z() - height());

        pkAgent->moveTo(kTarget);
    }
}

void FlockWidget::mouseMoveEvent(QMouseEvent* pkEvent)
{
    m_kFlock.setDisturbancePosition(pkEvent->x(), 0, pkEvent->y());
}

void FlockWidget::paintEvent(QPaintEvent* pkEvent)
{
    // Clear.
    QPainter kPainter(this);

    kPainter.fillRect(0, 0, width(), height(), Qt::white);

    // Paint.
    if(m_bQuadtreeVisible)
        paintRangesQuadtree(kPainter);

    if(m_bBodiesVisible)
        paintBodies(kPainter);

    if(m_bRangesVisible)
        paintRanges(kPainter);

    paintAgents(kPainter);

    kPainter.drawRect(0, 0, width() - 1, height() - 1);
}

void FlockWidget::resizeEvent(QResizeEvent* pkEvent)
{
    m_kFlock.setBounds(-s_uiSpanning,
                       -s_uiSpanning,
                       pkEvent->size().width() + s_uiSpanning * 2,
                       pkEvent->size().height() + s_uiSpanning * 2);
}

void FlockWidget::paintAgents(QPainter& rkPainter)
{
    rkPainter.save();

    rkPainter.setPen(QPen(Qt::black, 5));

    QSet< Agent* > kAgents = m_kFlock.agents();
    foreach(Agent* pkAgent, kAgents)
    {
        // Only paint active agents.
        if(!pkAgent->isActive())
            continue;

        // Paint agent.
        QVector3D kCenter = pkAgent->body().center();

        rkPainter.drawPoint(kCenter.x(), kCenter.z());
    }

    rkPainter.restore();
}

void FlockWidget::paintBodies(QPainter& rkPainter)
{
    rkPainter.save();

    rkPainter.setBrush(Qt::NoBrush);
    rkPainter.setPen(Qt::green);

    QSet< Agent* > kAgents = m_kFlock.agents();
    foreach(Agent* pkAgent, kAgents)
    {
        // Only paint active agents.
        if(!pkAgent->isActive())
            continue;

        // Paint body.
        QVector3D kCenter = pkAgent->body().center();
        qreal radius = pkAgent->body().radius();


        rkPainter.drawEllipse(kCenter.x() - radius,
                              kCenter.z() - radius,
                              radius * 2,
                              radius * 2);

    }

    rkPainter.restore();
}

void FlockWidget::paintRanges(QPainter& rkPainter)
{
    rkPainter.save();

    rkPainter.setBrush(Qt::NoBrush);
    rkPainter.setPen(Qt::cyan);

    QSet< Agent* > kAgents = m_kFlock.agents();
    foreach(Agent* pkAgent, kAgents)
    {
        // Only paint active agents.
        if(!pkAgent->isActive())
            continue;

        // Paint range.
        QVector3D kCenter = pkAgent->range().center();
        qreal radius = pkAgent->range().radius();

        rkPainter.drawEllipse(kCenter.x() - radius,
                              kCenter.z() - radius,
                              radius * 2,
                              radius * 2);
    }

    rkPainter.restore();
}

void FlockWidget::paintRangesQuadtree(QPainter& rkPainter)
{
    rkPainter.save();

    QColor kColor(0, 255, 255, 150);
    QColor kColorOpaque(0, 255, 255, 255);

    rkPainter.setPen(Qt::SolidLine);
    rkPainter.setPen(kColorOpaque);

    QSet< const Quadtree< Sphere >* > kLeaves = m_kFlock.ranges().leaves();
    foreach(const Quadtree< Sphere >* pkLeaf, kLeaves)
    {
        rkPainter.drawRect(pkLeaf->bounds());

        if(pkLeaf->count())
            rkPainter.fillRect(pkLeaf->bounds(), QBrush(kColor, Qt::BDiagPattern));
    }

    rkPainter.restore();
}
